using System;
using System.Collections.Generic;
using System.Text;

namespace LibGreen.Jabber.ProtocolElements
{
    /// <summary>
    /// Describes the presence of an XMPP entity or resource
    /// </summary>
    public enum Presence
    {
        /// <summary>
        /// The entity or resource is actively interested in chatting
        /// </summary>
        Available,
        /// <summary>
        /// The entity or resource is temporarily away
        /// </summary>
        Away,
        /// <summary>
        /// The entity or resource is busy
        /// </summary>
        DoNotDisturb,
        /// <summary>
        /// The entity or resource is away for an extended period
        /// </summary>
        ExtendedAway,
        /// <summary>
        /// No presence information was sent by the server
        /// </summary>
        /// <remarks>This value should not be sent by the client</remarks>
        None,
        /// <summary>
        /// The entity or resource is unavailable
        /// </summary>
        Unavailable
    }

    /// <summary>
    /// Defines the known values for the "type" attribute of a &lt;presence&gt; element
    /// </summary>
    public enum PresenceElementType
    {
        /// <summary>
        /// The element has no "type" attribute
        /// </summary>
        None,
        /// <summary>
        /// The resource identified by the "from" attribute is now unavailable
        /// </summary>
        Unavailable,
        /// <summary>
        /// A request to subscribe to the resource identified by the "to" attribute
        /// </summary>
        Subscribe,
        /// <summary>
        /// Approving a request for subscription by the resource identified by the "to" attribute
        /// </summary>
        Subscribed,
        /// <summary>
        /// A request to unsubscribe from the resource identified by the "to" attribute
        /// </summary>
        Unsubscribe,
        /// <summary>
        /// Rejecting a request for subscription by, or cancelling a subscription to, the resource identified by the "to" attribute
        /// </summary>
        Unsubscribed,
    }

    /// <summary>
    /// Describes the &lt;presence&gt; element
    /// </summary>
    public class PresenceElement
    {
        #region Static methods
        /// <summary>
        /// Converts a <see cref="Presence"/> enumeration to a valid &lt;show&gt; element value
        /// </summary>
        /// <param name="presence">The <see cref="Presence"/> setting to convert</param>
        /// <returns>A valid string representation of the setting</returns>
        private static string PresenceToString(Presence presence)
        {
            switch (presence)
            {
                case Presence.Available:
                case Presence.None:
                    return "chat";
                case Presence.Away:
                    return "away";
                case Presence.DoNotDisturb:
                    return "dnd";
                case Presence.ExtendedAway:
                    return "xa";
            }
            return "";
        }

        /// <summary>
        /// Converts a <see cref="PresenceElementType"/> enumeration to a valid &lt;type&gt; attribute value
        /// </summary>
        /// <param name="subscriptionType">The <see cref="PresenceElementType"/> setting to convert</param>
        /// <returns>A valid string representation of the setting</returns>
        private static string PresenceTypeToString(PresenceElementType subscriptionType)
        {
            switch (subscriptionType)
            {
                case PresenceElementType.Subscribe:
                    return "subscribe";
                case PresenceElementType.Subscribed:
                    return "subscribed";
                case PresenceElementType.Unavailable:
                    return "unavailable";
                case PresenceElementType.Unsubscribe:
                    return "unsubscribe";
                case PresenceElementType.Unsubscribed:
                    return "unsubscribed";
            }
            return "";
        }

        /// <summary>
        /// Converts the value of a &lt;show&gt; element to a <see cref="Presence"/> enumeration
        /// </summary>
        /// <param name="presence">The string represenation of the presence setting</param>
        /// <returns>A corresponding <see cref="Presence"/> member</returns>
        private static Presence StringToPresence(string presence)
        {
            switch (presence)
            {
                case "away":
                    return Presence.Away;
                case "dnd":
                    return Presence.DoNotDisturb;
                case "xa":
                    return Presence.ExtendedAway;
                case "chat":
                    return Presence.Available;
                default:
                    return Presence.None;
            }
        }

        /// <summary>
        /// Converts the value of the "type" attribute of a &lt;presence&gt; element to a <see cref="PresenceElementType"/> enumeration
        /// </summary>
        /// <param name="type">The string value of the "type" attribute</param>
        /// <returns>A corresponding <see cref="PresenceElementType"/> member</returns>
        private static PresenceElementType StringToPresenceType(string type)
        {
            switch (type)
            {
                case "unavailable":
                    return PresenceElementType.Unavailable;
                case "subscribe":
                    return PresenceElementType.Subscribe;
                case "subscribed":
                    return PresenceElementType.Subscribed;
                case "unsubscribe":
                    return PresenceElementType.Unsubscribe;
                case "unsubscribed":
                    return PresenceElementType.Unsubscribed;
                default:
                    return PresenceElementType.None;
            }
        }
        #endregion

        /// <summary>
        /// Creates a &lt;presence&gt; element for initial presence announcement
        /// </summary>
        /// <returns>A new &lt;presence&gt; element</returns>
        public static Element Create()
        {
            return Element.CreateElement("presence", new Dictionary<string, string>());
        }

        /// <summary>
        /// Creates a &lt;presence&gt; element for announcing changes in presence to the server
        /// </summary>
        /// <param name="from">The <see cref="Jid"/> of the client sending the element</param>
        /// <param name="presence">The <see cref="Presence"/> to set</param>
        /// <returns>A new &lt;presence&gt; element</returns>
        public static Element Create(Jid from, Presence presence)
        {
            return Create(from, presence, "", "");
        }

        /// <summary>
        /// Creates a &lt;presence&gt; element for announcing changes in presence to the server
        /// </summary>
        /// <param name="from">The <see cref="Jid"/> of the client sending the element</param>
        /// <param name="presence">The <see cref="Presence"/> to set</param>
        /// <param name="status">The status note to set</param>
        /// <returns>A new &lt;presence&gt; element</returns>
        public static Element Create(Jid from, Presence presence, string status)
        {
            return Create(from, presence, status, "");
        }

        /// <summary>
        /// Creates a &lt;presence&gt; element for announcing changes in presence to the server
        /// </summary>
        /// <param name="from">The <see cref="Jid"/> of the client sending the element</param>
        /// <param name="presence">The <see cref="Presence"/> to set</param>
        /// <param name="status">The status note to set</param>
        /// <param name="language">The value of the "xml:lang" attribute for the status message</param>
        /// <returns>A new &lt;presence&gt; element</returns>
        public static Element Create(Jid from, Presence presence, string status, string language)
        {
            Dictionary<string, string> attributes = new Dictionary<string, string>(3);
            Element element = Element.CreateElement("presence", attributes);
            attributes["from"] = from.ToString();
            if (presence == Presence.Unavailable)
            {
                attributes["type"] = "unavailable";
            }
            else
            {
                element.Children.Add(Element.CreateElement("show", new Dictionary<string, string>(), PresenceToString(presence)));
            }
            if (!String.IsNullOrEmpty(status))
            {
                Dictionary<string, string> statusAttributes = new Dictionary<string, string>();
                if (!String.IsNullOrEmpty(language))
                {
                    statusAttributes["xml:lang"] = language;
                }
                element.Children.Add(Element.CreateElement("status", statusAttributes, status));
            }
            return element;
        }

        /// <summary>
        /// Creates a &lt;presence&gt; element to handle subscriptions and unsubscriptions
        /// </summary>
        /// <param name="to">The <see cref="Jid"/> of the resource to which this subscription request is directed</param>
        /// <param name="subscriptionType">A <see cref="PresenceElementType"/> describing the subscription update request</param>
        /// <returns>A new &lt;presence&gt; element</returns>
        public static Element Create(Jid to, PresenceElementType subscriptionType)
        {
            Dictionary<string, string> attributes = new Dictionary<string, string>(3);
            attributes["to"] = to.ToString(false);
            attributes["type"] = PresenceTypeToString(subscriptionType);
            return Element.CreateElement("presence", attributes);
        }

        private readonly PresenceElementType type;
        private readonly string from;
        private readonly string to;
        private readonly string lang;
        private readonly int priority;
        // If the presence isn't stated explicitly, it's available
        private readonly Presence presenceIndicator = Presence.Available;
        private readonly Dictionary<string, string> statuses =
            new Dictionary<string, string>();

        /// <summary>
        /// Initializes an PresenceElement from a received <see cref="Element"/>
        /// </summary>
        /// <param name="element">The received <see cref="Element"/> containing the &lt;presence&gt; stanza</param>
        public PresenceElement(Element element)
        {
            string typeString = "";
            element.Attributes.TryGetValue("type", out typeString);
            type = StringToPresenceType(typeString);
            element.Attributes.TryGetValue("from", out from);
            element.Attributes.TryGetValue("to", out to);
            element.Attributes.TryGetValue("xml:lang", out lang);
            if (String.IsNullOrEmpty(lang))
            {
                lang = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
            }

            foreach (Element child in element.Children)
            {
                switch (child.Name)
                {
                    case "show":
                        presenceIndicator = StringToPresence(child.Value);
                        break;
                    case "status":
                        string statusLang;
                        child.Attributes.TryGetValue("xml:lang", out statusLang);
                        if (String.IsNullOrEmpty(statusLang))
                        {
                            statusLang = "default";
                        }
                        statuses.Add(statusLang, child.Value);
                        break;
                    case "priority":
                        Int32.TryParse(child.Value, out priority);
                        break;
                }
            }
        }

        /// <summary>
        /// Gets the value of the "type" attribute as a <see cref="PresenceElementType"/>
        /// </summary>
        public PresenceElementType Type
        {
            get { return type; }
        }

        /// <summary>
        /// Gets the resource that sent the presence information
        /// </summary>
        public string From
        {
            get { return from; }
        }

        /// <summary>
        /// Gets the destination resource of the presence information
        /// </summary>
        public string To
        {
            get { return to; }
        }

        /// <summary>
        /// Gets the two-letter ISO code for the default language of the status
        /// contained by this &lt;presence&gt; element
        /// </summary>
        public string DefaultLanguage
        {
            get { return lang; }
        }

        /// <summary>
        /// Gets the <see cref="Presence"/> indicator sent by the server, or <see cref="Presence.None"/>
        /// if the element does not contain presence information
        /// </summary>
        public Presence PresenceIndicator
        {
            get { return (type == PresenceElementType.Unavailable) ? Presence.Unavailable :  presenceIndicator; }
        }

        /// <summary>
        /// Gets the priority of this resource, or 0 if no presence was indicated
        /// </summary>
        public int Priority
        {
            get { return priority; }
        }

        /// <summary>
        /// Gets a status message by its language code, or the default status message if no corresponding language code
        /// is found, or "" if no status message was set by this presence element
        /// </summary>
        /// <param name="languageCode">The two-letter language code corresponding to a preferred xml:lang</param>
        /// <returns>A status message by its language code, or the default status message if no corresponding language code
        /// is found, or "" if no status message was set by this presence element</returns>
        public string GetStatusMessageByLanguage(string languageCode)
        {
            string retval = "";
            if (statuses.Count > 0)
            {
                if(!statuses.TryGetValue(languageCode, out retval))
                {
                    statuses.TryGetValue("default", out retval);
                }
            }
            return retval;
        }
    }
}
